﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;

namespace UMLTouch.Core.Entities
{
    public enum ShapeOpenTypeEnum
    {
        Closed,
        Opened,
        SemiClosed
    }

    /// <summary>
    /// The Shape Class represents a geometric shape made of segments and intersections. 
    /// The shape shall be recognized after usingt Shape patterns.
    /// </summary>
    public class Shape : IEngineEntity
    {
        double AngleTolerance = 15;

        #region Fields

        List<Segment> _segments;
        List<Intersection> _intersections;
        IShapePattern _shapePattern;
        Shape _subShape;




        int[] _anglesCounts = new int[3];

        Boolean _isDirty;

        #endregion

        #region Properties

        public Shape SubShape
        {
            get
            {
                Clean();
                return _subShape;
            }

        }

        /// <summary>
        /// Gets the number of distinct segments that constitue te shape
        /// </summary>
        public int NbSegments
        {
            get
            {
                Clean();
                return _segments.Count;
            }
        }
        /// <summary>
        /// Gets the amount of ExtremityIntersections along all the intersections in the shape
        /// </summary>
        public int NbExtremityIntersections
        {
            get
            {
                Clean();
                return (from i in _intersections.Select(i => i.Segments)
                        where i.Any(s => s.IntersectionType == IntersectionType.EndIntersection
                                    || s.IntersectionType == IntersectionType.StartIntersection)
                        select i).Count();
            }
        }
        /// <summary>
        /// Gets the amount of MiddleIntersections along all the intersections in the shape
        /// </summary>
        public int NbMiddleIntersections
        {
            get
            {
                Clean();
                return (from i in _intersections.Select(i => i.Segments)
                        where i.Any(s => s.IntersectionType == IntersectionType.MiddleIntersection)
                        select i).Count();
            }
        }
       
        public ShapeOpenTypeEnum ShapeOpenType
        {
            get
            {

                if (NbExtremityIntersections == NbSegments)
                {
                    return ShapeOpenTypeEnum.Closed;
                }
                else if (SubShape != null)
                {
                    return ShapeOpenTypeEnum.SemiClosed;
                }
                else
                {
                    return ShapeOpenTypeEnum.Opened;
                }
            }
        }
       
        public int NbAngleGreaterThan90
        {
            get
            {
                Clean();
                return _anglesCounts[0];
            }
        }
        
        public int NbAnglesEqual90
        {
            get
            {
                Clean();
                return _anglesCounts[1];

            }
        }
        
        public int NbAnglesLesserThan90
        {
            get
            {
                Clean();
                return _anglesCounts[2];

            }
        }

        /// <summary>
        /// The Shape pattern objects has to remain null until the shape has been recognized. 
        /// The propertie shall then be set with the corresponding pattern.
        /// </summary>
        public IShapePattern ShapePattern
        {
            get { return _shapePattern; }
            set { _shapePattern = value; }
        }

        /// <summary>
        /// Gets the List of the intersections in the shape
        /// </summary>
        public IEnumerable<Intersection> Intersections
        {
            get { return _intersections; }
        }
        /// <summary>
        /// gets the list of the segments in the shape
        /// </summary>
        public IEnumerable<Segment> Segments
        {
            get { return _segments; }
        }
        
        public IEnumerable<System.Windows.Ink.Stroke> BaseStrokes
        {
            get
            {
                return Segments.Select(s => s.Stroke).Distinct().Cast<System.Windows.Ink.Stroke>();
            }
        }

        public Point TopLeft
        {
            get
            {
                var xMin = Segments.SelectMany(seg => seg.Points).OrderBy(p => p.X).First();
                var yMin = Segments.SelectMany(seg => seg.Points).OrderBy(p => p.Y).First();

                return new Point(xMin.X, yMin.Y);
            }
        }

        public Point BottomRight
        {
            get
            {
                var xMax = Segments.SelectMany(seg => seg.Points).OrderByDescending(p => p.X).First();
                var yMax = Segments.SelectMany(seg => seg.Points).OrderByDescending(p => p.Y).First();

                return new Point(xMax.X, yMax.Y);
            }
        }

        #endregion

        #region Constructors

        public Shape()
        {
            _intersections = new List<Intersection>();
            _segments = new List<Segment>();
            _shapePattern = null;
            _isDirty = true;
        }

        public Shape(IEnumerable<Segment> segments)
            : this()
        {
            AddRangeSegments(segments);
        }

        public Shape(Shape shape)
            : this()
        {
            _segments = shape._segments.ToArray().ToList();
            foreach (Intersection intersection in shape.Intersections)
            {
                _intersections.Add(new Intersection(intersection));
            }
            _shapePattern = shape.ShapePattern;
        }




        #endregion

        #region Methods

        /// <summary>
        /// Add a segment to the shape.
        /// </summary>
        /// <param name="segment">the segment to add</param>
        /// <returns>the number of intersections detected with this segment</returns>
        public int AddSegment(Segment segment)
        {
            int ret = 0;
            _segments.Add(segment);
            //when a segment is added, it has to be checked if there are new intersections existing with the other segments
            ret += DetectSegmentIntersections(segment);

            if (ret > 0)
                _isDirty = true;
            return ret;
        }

        /// <summary>
        /// Add a range of segments to the shape.
        /// </summary>
        /// <param name="segment">the segments to add</param>
        /// <returns>the number of intersections detected with this segment</returns>
        public int AddRangeSegments(IEnumerable<Segment> segments)
        {
            int ret = 0;
            _segments.AddRange(segments);

            foreach (Segment segment in segments)
            {
                ret += DetectSegmentIntersections(segment);
            }
            if (ret > 0)
                _isDirty = true;
            return ret;
        }

        public void Clean()
        {

            if (_isDirty)
            {
                List<Segment> removed = new List<Segment>();
                IEnumerable<Segment> subShape;
                Boolean changed = false;


                do
                {
                    int previousRemovedCount = removed.Count;

                    var intersections = from i in Intersections
                                            .Where(i => i.Segments.Count < 3)
                                            .Where(i => !i.Segments.Any(s => s.IntersectionType == IntersectionType.MiddleIntersection))
                                        let nbSegments =
                                            (
                                            from s in i.Segments
                                            select s.Segment
                                            ).Except(removed).Count()
                                        where nbSegments > 1
                                        select i;



                    var ends = (from i in intersections
                                from s in i.Segments
                                where s.IntersectionType == IntersectionType.EndIntersection
                                select s.Segment).Except(removed);

                    var starts = (from i in intersections
                                  from s in i.Segments
                                  where s.IntersectionType == IntersectionType.StartIntersection
                                  select s.Segment).Except(removed);


                    var allSegments = (from i in Intersections
                                       from s in i.Segments
                                       select s.Segment).Except(removed).Distinct();

                    subShape = (from end in ends
                                join start in starts on end equals start
                                select start
                                )
                                .Union(
                                     from i in Intersections.Where(i => i.Segments.Count > 2)
                                     from s in i.Segments
                                     select s.Segment
                                )
                                .Distinct();


                    removed.AddRange(allSegments.Except(subShape).Distinct().ToList());

                    changed = removed.Count != previousRemovedCount;

                } while (changed);

                if (subShape.Count() > 0)
                {
                    _subShape = new Shape(subShape);
                }
                else
                {
                    _subShape = null;
                }


                _anglesCounts[0] = (from intersection in _intersections
                                    from angle in intersection.Angles
                                    where angle > 90 + AngleTolerance
                                    select angle).Count();

                _anglesCounts[1] = (from intersection in _intersections
                                    from angle in intersection.Angles
                                    where 90 - AngleTolerance < angle && angle < 90 + AngleTolerance
                                    select angle).Count();

                _anglesCounts[2] = (from intersection in _intersections
                                    from angle in intersection.Angles
                                    where angle < 90 - AngleTolerance
                                    select angle).Count();

                _isDirty = false;
            }
        }

        /// <summary>
        /// Detects Intersections between the existing segments and the one specified as parameter.
        /// </summary>
        /// <param name="segment">the segment to check</param>
        /// <returns>the amount of detected intersections</returns>
        protected int DetectSegmentIntersections(Segment segment)
        {

            int ret = 0;

            //gets the list of all the segments that are in the range off the current one
            //except the current itself
            var q = _segments.Where(s =>
                !(s.Equals(segment) || s.IsInRange(segment))
                );

            //the current segment is then compared to all the other segments:
            foreach (Segment existingSegment in q)
            {

                if (segment != existingSegment)
                {
                    //detect the intersection between the segment and the existingSegment. if there is no intersection,
                    //the Intersects method returns null. otherwise an Intersection object is created.
                    Intersection newIntersection = segment.Intersects(existingSegment);


                    if (newIntersection != null)
                    {
                        //if an intersection has been detected, it shall be checked if an intersection does not allready
                        //exists on the same point : 
                        Intersection existing = _intersections.FirstOrDefault(i => i.Equals(newIntersection));

                        if (existing != null)
                        {
                            //if an intersection exists, the new one is concatenated to the existing one.
                            existing.Concat(newIntersection);

                        }
                        else
                        {
                            //if the Intersection doesn't exists, the new intersection is added to the shape.
                            _intersections.Add(newIntersection);

                        }
                        ret++;
                    }
                    newIntersection = null;
                }
            }
            return ret;
        }

        #endregion

    }
}
